package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.gateway.properties.AuthUrlProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

//鉴权，网关拦截每个请求，透传用户id
@Slf4j
@Component
public class UserAuthGatewayFilter implements GlobalFilter {

    @Autowired
    AuthUrlProperties authUrlProperties;

    AntPathMatcher pathMatcher = new AntPathMatcher();//路径匹配器

    //ServerWebExchange 包含: ServerHttpRequest ServerHttpResponse
    //chain : filter 过滤器链
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //当前路径
        String path = exchange.getRequest().getURI().getPath();
        log.info("请求开始：{}",path);

        //【1 静态资源放行】 /img/**  /js/**  /css/**
        long anyoneCount = authUrlProperties.getAnyoneUrl()
                .stream()
                .filter((pattern) ->
                        pathMatcher.match(pattern, path)
                )
                .count();
        if (anyoneCount>0){
            log.info("静态资源直接放行");
            return chain.filter(exchange);
        }

        //【2 某些访问路径直接拒绝】
        long denyCount = authUrlProperties.getDenyUrl()
                .stream()
                .filter((pattern) -> pathMatcher.match(pattern, path))
                .count();
        if (denyCount>0){
            log.warn("浏览器请求内部路径，直接打回");
            Result<String> result = Result.build("", ResultCodeEnum.PERMISSION);
            //返回错误json
            return responseJson(exchange,result);
        }

        //【3 需要登录权限才能访问】
        long authCount = authUrlProperties.getAuthUrl()
                .stream()
                .filter(pattern -> pathMatcher.match(pattern, path))
                .count();
        if (authCount>0){
            log.info("需要登录才能访问");
            //判断是否登录，需要看令牌token
            String token = getToken(exchange);
            //根据token，获取redis中登录的用户信息
            UserInfo userInfo = getUserInfo(token);
            //当token为null，或者token不为null，但是token是假的，查询不到用户信息
            if (StringUtils.isEmpty(token) || userInfo == null ){
                String loginPage = authUrlProperties.getLoginPage();
                //重定向到登录页面
                return redirectToPage(exchange,loginPage);
            }
        }

        //todo 透传用户id ，前提：如果能拿到用户信息，就透传id
        log.info("验证用户信息开始。透传用户ID");
        //先获取请求（头或cookie）带来的 token令牌，然后根据 token，获取用户信息
        UserInfo userInfo = getUserInfo(getToken(exchange));
        //透传用户id
        return userIdThrougth(exchange,userInfo,chain);
        //filter放行请求
/*        Mono<Void> filter = chain.filter(exchange).doFinally((item)->{log.info("请求结束");});
        return filter;*/
    }

    //透传用户id
    private Mono<Void> userIdThrougth(ServerWebExchange exchange, UserInfo userInfo, GatewayFilterChain chain){
        //1获取请求
        ServerHttpRequest.Builder builder = exchange.getRequest().mutate();

        //2将id放在请求头中
        if (userInfo!=null){
            log.info("用户登录信息放在请求头中，进行透传");
            builder.header(RedisConst.USER_ID_HEADER, userInfo.getId().toString()).build();
        }
        //3用户没有登录，使用临时id
        String tempId = getTempId(exchange);
        if (!StringUtils.isEmpty(tempId)){
            log.info("用户临时信息放在请求头中，进行透传");
            builder.header(RedisConst.TEMP_ID_HEADER,tempId).build();
        }
        //4放行
        return chain.filter(exchange);
    }
    //临时id
    private String getTempId(ServerWebExchange exchange){
        ServerHttpRequest request = exchange.getRequest();//获取请求
        String token = request.getHeaders().getFirst("userTempId");//获取请求头
        //判断令牌:令牌存在
        if (!StringUtils.isEmpty(token)){
            return token;
        }
        //不存在
        HttpCookie first = request.getCookies().getFirst("userTempId");
        if (first != null){
            return first.getValue();
        }
        return null;
    }

    //响应数据json
    private Mono<Void> responseJson(ServerWebExchange exchange, Result result) {
        //得到响应对象
        ServerHttpResponse response = exchange.getResponse();

        //得到数据的dateBuffer
        String json = JSON.toJSONString(result);//要返回的数据
        DataBuffer dataBuffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));//包装
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);//设置响应类型

        return response.writeWith(Mono.just(dataBuffer));//写一个数据出去，Mono发布一个dataBuffer
    }

    //重定向到登录页面
    private Mono<Void> redirectToPage(ServerWebExchange exchange, String loginPage) {
        //获取响应
        ServerHttpResponse response = exchange.getResponse();
        //当时所在的请求路径
        URI uri = exchange.getRequest().getURI();
        //重定向的地址  http://passport.gmall.com/login.html?originUrl=http://www.gmall.com/
        loginPage = loginPage + "?originUrl=" + uri.toString();
        //设置响应状态码 302 状态码
        response.setStatusCode(HttpStatus.FOUND);
        //设置响应头 Location位置：(新地址) http://passport.gmall.com/login.html
        response.getHeaders().set("Location", loginPage);
        return response.setComplete();
    }

    @Autowired
    StringRedisTemplate redisTemplate;
    //根据token，获取redis 中登录的用户信息 userInfo
    private UserInfo getUserInfo(String token){
        if (StringUtils.isEmpty(token)){
            return null;
        }
        String json = redisTemplate.opsForValue().get("login:user:"+token);
        if (StringUtils.isEmpty(json)){
            return null;
        }
        UserInfo userInfo = JSON.parseObject(json, UserInfo.class);
        return userInfo;
    }

    //获取 请求携带的 令牌token
    private String getToken(ServerWebExchange exchange){
        //1 获取请求头或者cookie携带的令牌标识
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("token");
        if (!StringUtils.isEmpty(token)){
            return token;
        }
        //2 如果请求头没有，就看cookie，
        HttpCookie first = request.getCookies().getFirst("token");
        if (first !=null){
            return first.getValue();
        }
        //3 如果没登录，而且cookie也是null，即请求头和cookie都没有
        return null;
    }
}
